package dijkstranew

import (
	"fmt"
	"strings"
)

var ErrNoPath = fmt.Errorf("no path")

type edge struct {
	node   string
	weight float64
}

func (e edge) String() string {
	return fmt.Sprintf("%s(%f)", e.node, e.weight)
}

type Graph struct {
	edges map[string][]edge
}

func NewGraph() *Graph {
	return &Graph{edges: make(map[string][]edge)}
}

func (g *Graph) DeepCopy() *Graph {
	gCopy := NewGraph()
	//log.Info(" =========== raw =========== ")
	//for k, _ := range g.edges {
	//	if strings.Contains(k, ":10021") {
	//		log.Info(g.edges[k])
	//	}
	//}
	//log.Info(" =========== === =========== ")
	for k, v := range g.edges {
		res := make([]edge, len(v), cap(v))
		copy(res, v)
		gCopy.edges[k] = res
	}
	return gCopy
}

func (g *Graph) AddEdge(origin, destiny string, weight float64) {
	g.edges[origin] = append(g.edges[origin], edge{node: destiny, weight: weight})
}
func (g *Graph) RemoveOneVertex(origin string) {
	for key, _ := range g.edges {
		if strings.Contains(key, fmt.Sprintf("%s", origin)) {
			delete(g.edges, key)
			continue
		}
		for i := 0; i < len(g.edges[key]); {
			if strings.Contains(g.edges[key][i].node, fmt.Sprintf("%s", origin)) {
				g.edges[key] = append(g.edges[key][:i], g.edges[key][i+1:]...)
			} else {
				i++
			}
		}
	}
}
func (g *Graph) RemoveVertex(origin string) {
	//log.Warnf("remove vertex %s:%s", origin)

	for key, _ := range g.edges {
		if strings.Contains(key, fmt.Sprintf(":%s", origin)) {
			delete(g.edges, key)
			continue
		}
		for i := 0; i < len(g.edges[key]); {
			if strings.Contains(g.edges[key][i].node, fmt.Sprintf(":%s", origin)) {
				g.edges[key] = append(g.edges[key][:i], g.edges[key][i+1:]...)
			} else {
				i++
			}
		}
	}
}
func (g *Graph) RemoveOneEdge(origin, destiny string) {
	//log.Warnf("remove one edge %s:%s", origin, destiny)
	for originKey, _ := range g.edges {
		if !strings.Contains(originKey, fmt.Sprintf("%s", origin)) {
			continue
		}
		for i := 0; i < len(g.edges[originKey]); {
			if strings.Contains(g.edges[originKey][i].node, fmt.Sprintf("%s", destiny)) {
				g.edges[originKey] = append(g.edges[originKey][:i], g.edges[originKey][i+1:]...)
			} else {
				i++
			}
		}
	}
}

func (g *Graph) RemoveEdge(origin, destiny string) {
	//log.Warnf("remove edge %s:%s", origin, destiny)

	for originKey, _ := range g.edges {
		if !strings.Contains(originKey, fmt.Sprintf(":%s", origin)) {
			continue
		}
		for i := 0; i < len(g.edges[originKey]); {
			if strings.Contains(g.edges[originKey][i].node, fmt.Sprintf(":%s", destiny)) {
				g.edges[originKey] = append(g.edges[originKey][:i], g.edges[originKey][i+1:]...)
			} else {
				i++
			}
		}
	}
}

func (g *Graph) Shortest(origin, destiny string) ([]string, float64, error) {
	if _, find := g.edges[origin]; !find {
		return nil, 0.0, ErrNoPath
	}
	if _, find := g.edges[destiny]; !find {
		return nil, 0.0, ErrNoPath
	}
	h := newHeap()
	h.push(path{value: 0, nodes: []string{origin}})
	visited := make(map[string]bool)
	for len(*h.values) > 0 {
		// Find the nearest yet to visit node
		p := h.pop()
		node := p.nodes[len(p.nodes)-1]

		if visited[node] {
			continue
		}

		if node == destiny {
			return p.nodes, p.value, nil
		}

		for _, e := range g.getEdges(node) {
			if !visited[e.node] {
				// We calculate the total spent so far plus the cost and the path of getting here
				h.push(path{value: p.value + e.weight, nodes: append([]string{}, append(p.nodes, e.node)...)})
			}
		}

		visited[node] = true
	}

	return nil, 0.0, ErrNoPath
}

func (g *Graph) getEdges(node string) []edge {
	return g.edges[node]
}
